import * as log4js from 'log4js';
import * as fs from 'fs-extra';
import { join } from 'path';
import Util from 'util';
import dayjs from 'dayjs';
import Chalk from 'chalk';
import StackTrace from 'stacktrace-js';
const LOG_DIR_NAME = '../../logs';

fs.ensureDirSync(join(__dirname, LOG_DIR_NAME));
void ['request', 'response', 'error'].forEach((t) => {
  fs.ensureDirSync(join(__dirname, LOG_DIR_NAME, t));
});

export enum LogLevel {
  ALL = 'ALL',
  MARK = 'MARK',
  TRACE = 'TRACE',
  DEBUG = 'DEBUG',
  INFO = 'INFO',
  WARN = 'WARN',
  ERROR = 'ERROR',
  FATAL = 'FATAL',
  OFF = 'OFF',
}

const resolvePath = (dir, filename) => join(__dirname, LOG_DIR_NAME, dir, filename);

class ContextTrace {
  constructor(
    public readonly context: string,
    public readonly path?: string,
    public readonly lineNumber?: number,
    public readonly columnNumber?: number
  ) {}
}

log4js.addLayout('sims', (config) => {
  let moduleName = '';
  let position = '';
  const messageList = [];
  return (logEvent: log4js.LoggingEvent) => {
    const { data } = logEvent;
    data.forEach((item) => {
      if (item instanceof ContextTrace) {
        moduleName = item.context;
        if (item.lineNumber && item.columnNumber) {
          position = `${item.lineNumber}:${item.columnNumber}`;
        }
      }
      if (typeof item !== 'string') {
        // 将对象转为字符串
        // value = Util.inspect(value, {
        //   showHidden: true, 显示类型为私有的属性
        //   depth: 3, 递归层级
        //   colors: true 使用颜色
        // })
        item = Util.inspect(item, false, 3, false);
      }
      messageList.push(item);
    });
    const msgOutput = messageList.join(' ');
    const positionOutput = position ? ` [${position}]` : '';
    const typeOutput = ` [${config.type}] ${logEvent.pid.toString()} - `;
    const dateOutput = ` [${dayjs(logEvent.startTime).format('YYYY-MM-DD HH:mm:ss')}]`;
    const moduleOutput = moduleName ? ` [${moduleName}]` : '[LoggerService]';
    let levelOutput = ` [${logEvent.level.toString()}] ${msgOutput}`;

    switch (logEvent.level.toString()) {
      case LogLevel.DEBUG:
        levelOutput = Chalk.green(levelOutput);
        break;
      case LogLevel.ERROR:
        levelOutput = Chalk.red(levelOutput);
        break;
      case LogLevel.FATAL:
        levelOutput = Chalk.hex('#DD4C35')(levelOutput);
        break;
      case LogLevel.INFO:
        levelOutput = Chalk.blue(levelOutput);
        break;
      case LogLevel.TRACE:
        levelOutput = Chalk.gray(levelOutput);
        break;
      case LogLevel.WARN:
        levelOutput = Chalk.yellow(levelOutput);
        break;
      default:
        levelOutput = Chalk.white(levelOutput);
        break;
    }
    return `${Chalk.green(typeOutput)}${dateOutput}  ${Chalk.yellow(
      moduleOutput
    )}${levelOutput}${positionOutput} `;
  };
});

const commonCinfig = {
  type: 'dateFile',
  pattern: '-yyyy-MM-dd.log',
  alwaysIncludePattern: true,
};

log4js.configure({
  appenders: {
    console: {
      type: 'console',
      layout: {
        type: 'sims',
      },
    },
    access: {
      type: 'dateFile',
      filename: resolvePath('access', 'access.log'),
      layout: {
        type: 'sims',
      },
      ...commonCinfig,
    },
    error: {
      type: 'dateFile',
      filename: resolvePath('error', 'error.log'),
      category: 'error',
      layout: {
        type: 'sims',
      },
      ...commonCinfig,
    },
    app: {
      type: 'dateFile',
      filename: resolvePath('app', 'app.log'),
      alwaysIncludePattern: true,
      layout: {
        type: 'pattern',
        pattern: '{"date":"%d","level":"%p","category":"%c","host":"%h","pid":"%z","data":\'%m\'}',
      },
      // 日志文件按日期（天）切割
      pattern: 'yyyyMMdd',
      daysToKeep: 60,
      // maxLogSize: 10485760,
      numBackups: 3,
      keepFileExt: true,
    },
    request: {
      ...commonCinfig,
      filename: resolvePath('request', 'request.log'),
      category: 'request',
    },
    response: {
      ...commonCinfig,
      filename: resolvePath('response', 'response.log'),
      category: 'response',
    },
  },
  categories: {
    default: {
      appenders: ['console', 'app', 'error', 'request'],
      level: 'DEBUG',
    },
    info: { appenders: ['console', 'app', 'error'], level: 'info' },
    error: { appenders: ['console', 'app', 'error'], level: 'error' },
    access: { appenders: ['console', 'app', 'error'], level: 'info' },
    http: { appenders: ['access'], level: 'DEBUG' },
  },
  pm2: true, // 使用 pm2 来管理项目时，打开
  pm2InstanceVar: 'INSTANCE_ID', // 会根据 pm2 分配的 id 进行区分，以免各进程在写日志时造成冲突
});
const logger = log4js.getLogger();
logger.level = LogLevel.TRACE;
export class Logger {
  static trace(...args) {
    logger.trace(Logger.getStackTrace(), ...args);
  }
  static debug(...args) {
    logger.debug(Logger.getStackTrace(), ...args);
  }
  static info(...args) {
    logger.info(Logger.getStackTrace(), ...args);
  }
  static warn(...args) {
    logger.warn(Logger.getStackTrace(), ...args);
  }
  static error(...args) {
    logger.error(Logger.getStackTrace(), ...args);
  }
  static fatal(...args) {
    logger.fatal(Logger.getStackTrace(), ...args);
  }
  static access(...args) {
    const loggerCustom = log4js.getLogger('http');
    loggerCustom.info(Logger.getStackTrace(), ...args);
  }
  static getStackTrace(deep = 2) {
    const stackList: StackTrace.StackFrame[] = StackTrace.getSync();
    const stackInfo: StackTrace.StackFrame = stackList[deep];
    const { fileName, lineNumber, columnNumber } = stackInfo;
    const basename: string = fileName.split('sims-nest/')[1];
    return `
    ${basename} ${lineNumber}:${columnNumber}
    `;
  }
}
